(function(_global) {
    var NATIVE_METHODS_OBJECT = [
	"toString", "toLocaleString", "valueOf",
	"hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable"
    ];
    var NATIVE_METHODS_FUNCTION = [
	"toString", "apply", "call", "bind"
    ];
    var NATIVE_METHODS_ARRAY = [
	"toString", "toLocaleString", "concat", "join", "pop", "push", "reverse", "shift",
	"slice", "sort", "splice", "unshift", "indexOf",
	"lastIndexOf", "every", "some", "forEach", "map",
	"filter", "reduce", "reduceRight"
    ];
    var NATIVE_METHODS_STRING = [
	"toString", "valueOf", "charAt", "charCodeAt", "concat", "indexOf",
	"lastIndexOf", "localeCompare", "match", "replace",
	"search", "slice", "split", "substring", "toLowerCase",
	"toLocaleLowerCase", "toUpperCase", "toLocaleUpperCase",
	"trim", "toJSON"
    ];
    var NATIVE_METHODS_BOOLEAN = [
	"toString", "valueOf", "toJSON"
    ];
    var NATIVE_METHODS_NUMBER = [
	"toString", "toLocaleString", "valueOf", "toFixed", "toExponential", "toPrecision", "toJSON"
    ];
    var NATIVE_METHODS_MATH = [
	"abs", "acos", "asin", "atan", "atan2", "ceil",
	"cos", "exp", "floor", "log", "pow", "round",
	"sin", "sqrt", "tan"
    ];
    var NATIVE_METHODS_DATE = [
	"toString", "toDateString", "toTimeString", "toLocaleString", "toLocaleDateString",
	"toLocaleTimeString", "valueOf", "getTime", "getFullYear", "getUTCFullYear",
	"getMonth", "getUTCMonth", "getDate", "getUTCDate", "getDay", "getUTCDay",
	"getHours", "getUTCHours", "getMinutes", "getUTCMinutes", "getSeconds",
	"getUTCSeconds", "getMilliseconds", "getUTCMilliseconds", "getTimezoneOffset",
	"setTime", "setFullYear", "setUTCFullYear",
	"setMonth", "setUTCMonth", "setDate", "setUTCDate", "setDay", "setUTCDay",
	"setHours", "setUTCHours", "setMinutes", "setUTCMinutes", "setSeconds",
	"setUTCSeconds", "setMilliseconds", "setUTCMilliseconds",
	"toUTCString", "toISOString", "toJSON"
    ];
    var NATIVE_METHODS_REGEXP = [
	"exec", "test", "toString"
    ];
    var RE_SPACE = /%20/g;
    var extendedClasses = {
    };

    function extendObject(value) {
	if (value === undefined) {
	    return new XUndefined(value);
	} else if (value === null) {
	    return new XNull(value);
	}
	switch (typeof value) {
	case "string":
	    return new XString(value);
	case "number":
	    return new XNumber(value);
	case "boolean":
	    return new XBoolean(value);
	case "function":
	    return new XFunction(value);
	case "object":
	    if (value instanceof Array) {
		return new XArray(value);
	    } else if (value instanceof Date) {
		return new XDate(value);
	    } else if (value instanceof RegExp) {
		return new XRegExp(value);
	    } else {
		// user extended classes
		for (var className in extendedClasses) {
		    var type = extendedClasses[className];
		    if (type.isType(value)) {
			return new type.clazz(value);
		    }
		}
		return new XObject(value);
	    }
	}
    }
    // utilities (private)
    var slice = Array.prototype.slice;

    function update(array, args) {
	var arrayLength = array.length, length = args.length;
	while (length--) array[arrayLength + length] = args[length];
	return array;
    }
    function merge(array, args) {
	array = slice.call(array, 0);
	return update(array, args);
    }
    function toArray(arrayLike) {
	return slice.call(arrayLike, 0);
    }
    function extend(dest, src) {
	for (var p in src) {
	    dest[p] = src[p];
	}
	return dest;
    }
    function overrideMethodsByNative(extendedType, baseType, methodNames) {
	for (var i = 0; i < methodNames.length; i++) {
	    var methodName = methodNames[i];
	    var method = baseType.prototype[methodName];
	    if (typeof method === "function") {
		extendedType.prototype[methodName] = (function(method) {
		    return function() {
			return method.apply(this.__val, toArray(arguments));
		    };
		})(method);
	    }
	}
    }

    // ===== extend built-in object
    
    // X.Object.extend(name, method, generateX, override)
    // X.Object.extend(proto, generateX, override)
    function extendClass() {
	var name, method, proto, generateX, override;
	if (typeof arguments[0] === "string") {
	    name = arguments[0];
	    method = arguments[1];
	    generateX = arguments[2];
	    override = arguments[3];
	} else {
	    proto = arguments[0];
	    generateX = arguments[1];
	    override = arguments[2];
	}
	if (proto) {
	    for (var p in proto) {
		extendClass.apply(this, [p, proto[p], generateX, override]);
	    }
	} else {
	    var original = this.prototype[name];
	    if (original && override === false) {
		throw new Error("cannot override this method:[" + name + "]");
	    }
	    this.prototype[name] = method;
	    if (generateX) {
		this.prototype[name + "X"] = function() {
		    return X(method.apply(this, toArray(arguments)));
		};
	    }
	}
    }
    // class
    function XClass() {
	var clazz = function(value) {
	    this.__val = value;
	};
	clazz.extend = function() {
	    extendClass.apply(clazz, toArray(arguments));
	};
	return clazz;
    }
    //object
    var XObject = XClass();
    XObject.extend({
	isExtended: function() {
	    return true;
	},
	isUndefined: function() {
	    return false;
	},
	isNull: function() {
	    return false;
	},
	isString: function() {
	    return false;
	},
	isNumber: function() {
	    return false;
	},
	isBoolean: function() {
	    return false;
	},
	isFunction: function() {
	    return false;
	},
	isArray: function() {
	    return false;
	},
	isDate: function() {
	    return false;
	},
	isRegExp: function() {
	    return false;
	},
	isEmpty: function() {
	    for (var key in this.__val) {
		return false;
	    }
	    return true;
	},
	extend: function(dest) {
	    return extend(dest, this.__val);
	},
	val: function() {
	    return this.__val;
	},
	keys: function() {
	    var val = this.__val;
	    var results = [];
	    for (var property in val)
		results.push(property);
	    return results;
	},
	contains: function(val) {
	    return this.containsValue(val);
	},
	containsValue: function(val) {
	    var obj = this.__val;
	    for (var key in obj) {
		if (obj[key] == val) {
		    return true;
		}
	    }
	    return false;
	},
	containsKey: function(key) {
	    return key in this.__val;
	},
	values: function() {
	    var val = this.__val;
	    var results = [];
	    for (var property in val)
		results.push(val[property]);
	    return results;
	},
	transpost: function() {
	    var obj = this.__val;
	    var transposed = {};
	    for (var key in obj) {
		transposed[obj[key]] = key;
	    }
	    return transposed;
	},
	clear: function() {
	    // Some versions of IE has problems if we delete keys from the beginning
	    var keys = this.keys();
	    for (var i = keys.length - 1; i >= 0; i--) {
		this.remove(keys[i]);
	    }
	},
	remove: function(key) {
	    var obj = this.__val;
	    var rv;
	    if ((rv = key in obj)) {
		delete obj[key];
	    }
	    return rv;
	},
	clone: function() {
	    return this.extend({});
	},
	params: function() {
	    var params = [];
	    function buildParams(name, value) {
		if (value instanceof Array) {
		    X(value).each(function(val, index) {
			buildParams(name, val);
		    });
		} else {
		    name = String(name), value = String(value);
		    params.push(encodeURIComponent(name) + "=" + encodeURIComponent(value));
		}
	    }
	    this.each(function(value, name) {
		buildParams(name, value);
	    });
	    return params.join("&").replace(RE_SPACE, "+");
	},
	join: function(nameValDelim, tokenDelim) {
	    if (nameValDelim === undefined) {
		nameValDelim = "=";
	    }
	    if (tokenDelim === undefined) {
		tokenDelim = ",";
	    }
	    var tokens = [];
	    function buildTokens(name, value) {
		if (value instanceof Array) {
		    X(value).each(function(val, index) {
			buildTokens(name, val);
		    });
		} else {
		    name = String(name), value = String(value);
		    tokens.push(name + nameValDelim + value);
		}
	    }
	    this.each(function(value, name) {
		buildTokens(name, value);
	    });
	    return tokens.join(tokenDelim);
	}
    }, true);
    // we don't extend for these functions
    XObject.extend({
	each: function(f, opt_obj) {
	    this.forEach(f, opt_obj);
	},
	forEach: function(f, opt_obj) {
	    var obj = this.__val;
	    for (var key in obj) {
		f.call(opt_obj, obj[key], key, obj);
	    }
	},
	filter: function(f, opt_obj) {
	    var res = {};
	    this.forEach(function(value, key) {
		if (f.call(opt_obj, obj[key], key, obj)) {
		    res[key] = obj[key];
		}
	    });
	    return res;
	},
	map: function(f, opt_obj) {
	    var obj = this.__val;
	    var res = {};
	    for (var key in obj) {
		res[key] = f.call(opt_obj, obj[key], key, obj);
	    }
	    return res;
	},
	some: function(f, opt_obj) {
	    var obj = this.__val;
	    for (var key in obj) {
		if (f.call(opt_obj, obj[key], key, obj)) {
		    return true;
		}
	    }
	    return false;
	},
	every: function(f, opt_obj) {
	    var obj = this.__val;
	    for (var key in obj) {
		if (!f.call(opt_obj, obj[key], key, obj)) {
		    return false;
		}
	    }
	    return true;
	},
	findKey: function(f, opt_this) {
	    var obj = this.__val;
	    for (var key in obj) {
		if (f.call(opt_this, obj[key], key, obj)) {
		    return key;
		}
	    }
	    return undefined;
	},
	findValue: function(f, opt_this) {
	    var key = this.findKey(f, opt_this);
	    return key && this.__val[key];
	}
    }, false);
    overrideMethodsByNative(XObject, Object, NATIVE_METHODS_OBJECT);

    // string
    var XString = XClass();
    XString.extend(XObject.prototype);
    XString.extend({
	isString: function() {
	    return true;
	},
	startsWith: function(pattern) {
	    return this.__val.indexOf(pattern) === 0;
	},
	startsWithIgnoreCase: function(pattern) {
	    pattern = String(pattern);
	    var str1 = this.__val.substr(0, pattern.length).toLowerCase();
	    var str2 = pattern.toLowerCase();
	    return str1 === str2;
	},
	endsWith: function endsWith(pattern) {
	    var val = this.__val;
	    var d = val.length - pattern.length;
	    return d >= 0 && val.lastIndexOf(pattern) === d;
	},
	endsWithIgnoreCase: function(pattern) {
	    pattern = String(pattern);
	    var val = this.__val;
	    var d = val.length - pattern.length;
	    if (d >= 0) {
		return val.substr(d) === pattern.toLowerCase();
	    }
	    return false;
	},
	times: function(count) {
	    return count < 1 ? '' : new Array(count + 1).join(this.__val);
	},
	truncate: function(length, truncation) {
	    var str = this.__val;
	    length = length || 30;
	    truncation = truncation === undefined ? '...' : truncation;
	    return str.length > length ?
		str.slice(0, length - truncation.length) + truncation : String(str);
	},
	strip: function() {
	    return this.__val.replace(/^\s+/, '').replace(/\s+$/, '');
	},
	stripTags: function() {
	    return this.__val.replace(/<\w+(\s+(\"[^\"]*\"|\'[^\']*\'|[^>])+)?>|<\/\w+>/gi, '');
	},
	escapeHTML: function() {
	    return this.__val.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
	},
	unescapeHTML: function() {
	    return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
	},
	camelize: function() {
	    var str = this.__val;
	    var parts = str.split('-'), len = parts.length;
	    if (len == 1) return parts[0];
	    var camelized = str.charAt(0) == '-'
		? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
		: parts[0];
	    for (var i = 1; i < len; i++)
		camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
	    return camelized;
	},
	capitalize: function() {
	    var str = this.__val;
	    return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
	},
	indexOfIgnoreCase: function(str) {
	    return this.__val.toLowerCase().indexOf(str.toLowerCase());
	},
	lastIndexOfIgnoreCase: function(str) {
	    return this.__val.toLowerCase().lastIndexOf(str.toLowerCase());
	},
	isEmpty: function() {
	    return this.__val.length === 0;
	},
	isBlank: function() {
	    return /^[\s\xa0]*$/.test(this.__val);
	},
	isBreakingWhitespace: function() {
	    return !/[^\t\n\r ]/.test(this.__val);
	},
	isAlpha: function() {
	    return !/[^a-zA-Z]/.test(this.__val);
	},
	isNumeric: function() {
	    return !/[^0-9]/.test(this.__val);
	},
	isAlphaNumeric: function() {
	    return !/[^a-zA-Z0-9]/.test(this.__val);
	},
	isSpace: function() {
	    return this.__val === ' ';
	},
	isUnicodeChar: function() {
	    var ch = this.__val;
	    return ch.length == 1 && ch >= ' ' && ch <= '~' ||
		ch >= '\u0080' && ch <= '\uFFFD';
	},
	compareIgnoreCase: function(str2) {
	    var str1 = this.__val;
	    var test1 = String(str1).toLowerCase();
	    var test2 = String(str2).toLowerCase();
	    if (test1 < test2) {
		return -1;
	    } else if (test1 == test2) {
		return 0;
	    } else {
		return 1;
	    }
	},
	trim: function() {
	    // Since IE doesn't include non-breaking-space (0xa0) in their \s character
	    // class (as required by section 7.2 of the ECMAScript spec), we explicitly
	    // include it in the regexp to enforce consistent cross-browser behavior.
	    return this.__val.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '')
	},
	trimLeft: function() {
	    // Since IE doesn't include non-breaking-space (0xa0) in their \s character
	    // class (as required by section 7.2 of the ECMAScript spec), we explicitly
	    // include it in the regexp to enforce consistent cross-browser behavior.
	    return this.__val.replace(/^[\s\xa0]+/, '');
	},
	trimRight: function() {
	    // Since IE doesn't include non-breaking-space (0xa0) in their \s character
	    // class (as required by section 7.2 of the ECMAScript spec), we explicitly
	    // include it in the regexp to enforce consistent cross-browser behavior.
	    return this.__val.replace(/[\s\xa0]+$/, '');
	},
	subs: function(var_args) {
	    var str = this.__val;
	    // This appears to be slow, but testing shows it compares more or less
	    // equivalent to the regex.exec method.
	    for (var i = 1; i < arguments.length; i++) {
		// We cast to String in case an argument is a Function.  Replacing $&, for
		// example, with $$$& stops the replace from subsituting the whole match
		// into the resultant string.  $$$& in the first replace becomes $$& in the
		//  second, which leaves $& in the resultant string.  Also:
		// $$, $`, $', $n $nn
		var replacement = String(arguments[i]).replace(/\$/g, '$$$$');
		str = str.replace(/\%s/, replacement);
	    }
	    return str;
	},
	newLineToBr: function(opt_xml) {
	    return this.__val.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br/>' : '<br>');
	},
	inspect: function(useDoubleQuotes) {
	    var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) {
		if (character in String.specialChar) {
		    return String.specialChar[character];
		}
		return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
	    });
	    if (useDoubleQuotes) {
		return '"' + escapedString.replace(/\"/g, '\\"') + '"';
	    } else {
		return "'" + escapedString.replace(/\'/g, '\\\'') + "'";
	    }
	},
	padLeft: function(ch, size) {
	    var str = this.__val;
	    var n = size - str.length;
	    for (var i = 0; i < n; i++) {
		str = ch + str;
	    }
	    return str;
	},
	padRight: function(ch, size) {
	    var str = this.__val;
	    var n = size - str.length;
	    for (var i = 0; i < n; i++) {
		str += ch;
	    }
	    return str;
	},
	repeat: function(count) {
	    var arr = [], str = this.__val;
	    X(count).times(function() {
		arr.push(str);
	    });
	    return arr.join("");
	},
	reverse: function() {
	    var arr = [], str = this.__val;
	    for (var i = str.length; i >= 0; i--) {
		arr.push(str.charAt(i));
	    }
	    return arr.join("");
	},
	left: function(len) {
	    var str = this.__val;
	    len = Math.min(len, str.length);
	    return str.substr(0, len);
	},
	right: function(len) {
	    var str = this.__val;
	    len = Math.min(len, str.length);
	    return str.substr(str.length - len);
	},
	namespace: function() {
	    var current = _global;
	    X(this.val().split(".")).each(function(nm) {
		if (current[nm] === undefined) {
		    current[nm] = {};
		}
		current = current[nm];
	    });
	    return current;
	}
    }, true);
    XString.extend({
	eachChar: function(f, opt_obj) {
	    var str = this.__val;
	    for (var i = 0, n = str.length; i < n; i++) {
		f.call(opt_obj, str.charAt(i));
	    }
	}
    }, false);
    overrideMethodsByNative(XString, String, NATIVE_METHODS_STRING);

    // undefined
    var XUndefined = XClass();
    XUndefined.extend(XObject.prototype);
    XUndefined.extend({
	isUndefined: function() {
	    return true;
	},
	isEmpty: function() {
	    return true;
	},
	toString: function() {
	    return "undefined";
	}
    }, true);

    // null
    var XNull = XClass();
    XNull.extend(XObject.prototype);
    XNull.extend({
	isNull: function() {
	    return true;
	},
	isEmpty: function() {
	    return true;
	},
	toString: function() {
	    return "null";
	}
    }, true);

    var XNumber = XClass();
    XNumber.extend(XObject.prototype);
    XNumber.extend({
	isNumber: function() {
	    return true;
	},
	toPaddedString: function(length, radix) {
	    var n = this.__val;
	    var string = n.toString(radix || 10);
	    return X("0").times(length - string.length) + string;
	}
    }, true, true);
    XNumber.extend({
	times: function(fn, ctx) {
	    var count = this.__val;
	    for (var i = 0; i < count; i++) {
		fn.call(ctx, i);
	    }
	}
    }, false, true);
    overrideMethodsByNative(XNumber, Number, NATIVE_METHODS_NUMBER);
    (function addMathMethods() {
	for (var i = 0; i < NATIVE_METHODS_MATH.length; i++) {
	    var methodName = NATIVE_METHODS_MATH[i];
	    var method = Math[methodName];
	    if (typeof method === "function") {
		XNumber.extend(methodName, (function(_method) {
		    return function() {
			return _method.call(Math, this.__val);
		    };
		})(method), true, true);
	    }
	}
    })();
    
    //boolean
    var XBoolean = XClass();
    XBoolean.extend(XObject.prototype);
    XBoolean.extend({
	isBoolean: function() {
	    return true;
	}
    }, true);
    overrideMethodsByNative(XBoolean, Boolean, NATIVE_METHODS_BOOLEAN);
    
    //function
    var XFunction = XClass();
    XFunction.extend(XObject.prototype);
    XFunction.extend({
	isFunction: function() {
	    return true;
	},
	bind: function(context) {
	    if (arguments.length < 2 && arguments[0] === undefined) return this.__val;
	    var __method = this.__val, args = slice.call(arguments, 1);
	    return function() {
		var a = merge(args, arguments);
		return __method.apply(context, a);
	    }
	},
	argumentNames: function() {
	    var names = this.__val.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
	    .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
	    .replace(/\s+/g, '').split(',');
	    return names.length == 1 && !names[0] ? [] : names;
	},
	curry: function() {
	    if (!arguments.length) return this.__val;
	    var __method = this.__val, args = slice.call(arguments, 0);
	    return function() {
		var a = merge(args, arguments);
		return __method.apply(this, a);
	    }
	},
	delay: function(timeout) {
	    var __method = this.__val, args = slice.call(arguments, 1);
	    return setTimeout(function() {
		return __method.apply(__method, args);
	    }, timeout);
	},
	defer: function() {
	    var args = update([0.01], arguments);
	    return this.delay.apply(this, args);
	},
	wrap: function(wrapper) {
	    var __method = this.__val;
	    return function() {
		var a = update([__method.bind(this)], arguments);
		return wrapper.apply(this, a);
	    }
	}
    }, true);
    overrideMethodsByNative(XFunction, Function, NATIVE_METHODS_FUNCTION);
    
    // Array
    var XArray = XClass();
    XArray.extend(XObject.prototype);
    XArray.extend({
	isArray: function() {
	    return true;
	},
	indexOf: function(obj, opt_fromIndex) {
	    var arr = this.__val;
	    var fromIndex = opt_fromIndex == null?
		0 : (opt_fromIndex < 0 ?
		     Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
	    for (var i = fromIndex; i < arr.length; i++) {
		if (i in arr && arr[i] === obj)
		    return i;
	    }
	    return -1;
	},
	lastIndexOf: function(obj, opt_fromIndex) {
	    var arr = this.__val;
	    var fromIndex = opt_fromIndex == null?
		0 : (opt_fromIndex < 0 ?
		     Math.max(0, arr.length + opt_fromIndex) : opt_fromIndex);
	    for (var i = fromIndex; i >= 0; i--) {
		if (i in arr && arr[i] === obj)
		    return i;
	    }
	    return -1;
	},
	pluck: function(property) {
	    var results = [];
	    this.each(function(value) {
		results.push(value[property]);
	    });
	    return results;
	},
	contains: function(obj) {
	    return this.indexOf(obj) >= 0;
	},
	isEmpty: function() {
	    return this.__val.length === 0;
	},
	clear: function() {
	    this.__val.length = 0;
	},
	insert: function(obj) {
	    if (!this.contains(obj)) {
		this.__val.push(obj);
	    }
	},
	insertAt: function(obj, opt_index) {
	    this.splice(opt_index, 0, obj);
	},
	insertBefore: function(obj, opt_obj2) {
	    var i;
	    if (arguments.length == 2 || (i = this.indexOf(opt_obj2)) < 0) {
		this.__val.push(obj);
	    } else {
		this.insertAt(obj, i);
	    }
	},
	splice: function(index, howMany, var_args) {
	    return Array.prototype.splice.apply(
		this.__val, slice(arguments, 1));
	},
	flatten: function() {
	    var result = [];
	    var arr = this.__val;
	    for (var i = 0, n = arr.length; i < n; i++) {
		if (arr[i] instanceof Array) {
		    result.push.apply(result, X(arr[i]).flatten());
		} else {
		    result.push(arr[i]);
		}
	    }
	    return result;
	},
	rotate: function(n) {
	    var array = this.__val;
	    if (array.length) {
		n %= array.length;
		if (n > 0) {
		    Array.prototype.unshift.apply(array, array.splice(-n, n));
		} else if (n < 0) {
		    Array.prototype.push.apply(array, array.splice(0, -n));
		}
	    }
	    return array;
	},
	remove: function(obj) {
	    var i = this.indexOf(obj);
	    var rv;
	    if ((rv = i >= 0)) {
		this.removeAt(i);
	    }
	    return rv;
	},
	removeAt: function(i) {
	    // use generic form of splice
	    // splice returns the removed items and if successful the length of that
	    // will be 1
	    return Array.prototype.splice.call(this.__val, i, 1).length == 1;
	}
    }, true);
    
    // we don't need the X function
    XArray.extend({
	each: function(fn, ctx) {
	    this.forEach(fn, ctx);
	},
	forEach: function(fn, ctx) {
	    var array = this.__val;
	    var l = array.length;
	    for (var i = 0; i < l; i++) {
		if (i in array) {
		    fn.call(ctx, val[i], i, array);
		}
	    }
	},
	forEachRight: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    for (var i = l - 1; i >= 0; --i) {
		if (i in arr) {
		    f.call(opt_obj, arr[i], i, arr);
		}
	    }
	},
	filter: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    var res = [];
	    var resLength = 0;
	    for (var i = 0; i < l; i++) {
		if (i in arr) {
		    var val = arr[i];  // in case f mutates arr2
		    if (f.call(opt_obj, val, i, arr)) {
			res[resLength++] = val;
		    }
		}
	    }
	    return res;
	},
	map: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    var res = new Array(l);
	    for (var i = 0; i < l; i++) {
		if (i in arr) {
		    res[i] = f.call(opt_obj, arr[i], i, arr);
		}
	    }
	    return res;
	},
	reduce: function(f, val, opt_obj) {
	    var arr = this.__val;
	    var rval = val;
	    this.forEach(function(val, index) {
		rval = f.call(opt_obj, rval, val, index, arr);
	    });
	    return rval;
	},
	reduceRight: function(f, val, opt_obj) {
	    var arr = this.__val;
	    var rval = val;
	    X(arr).forEachRight(arr, function(val, index) {
		rval = f.call(opt_obj, rval, val, index, arr);
	    });
	    return rval;
	},
	some: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    for (var i = 0; i < l; i++) {
		if (i in arr && f.call(opt_obj, arr[i], i, arr)) {
		    return true;
		}
	    }
	    return false;
	},
	every: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    for (var i = 0; i < l; i++) {
		if (i in arr && !f.call(opt_obj, arr[i], i, arr)) {
		    return false;
		}
	    }
	    return true;
	},
	find: function(f, opt_obj) {
	    var i = this.findIndex(f, opt_obj);
	    return i < 0 ? null : this.__val[i];
	},
	findIndex: function(arr, f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;
	    for (var i = 0; i < l; i++) {
		if (i in arr && f.call(opt_obj, arr[i], i, arr)) {
		    return i;
		}
	    }
	    return -1;
	},
	findRight: function(f, opt_obj) {
	    var i = this.findIndexRight(f, opt_obj);
	    return i < 0 ? null : this.__val[i];
	},
	findIndexRight: function(f, opt_obj) {
	    var arr = this.__val;
	    var l = arr.length;  // must be fixed during loop... see docs
	    for (var i = l - 1; i >= 0; i--) {
		if (i in arr && f.call(opt_obj, arr[i], i, arr)) {
		    return i;
		}
	    }
	    return -1;
	},
	removeIf: function(f, opt_obj) {
	    var arr = this.__val;
	    var i = this.findIndex(f, opt_obj);
	    if (i >= 0) {
		this.removeAt(arr, i);
		return true;
	    }
	    return false;
	}
    }, false);
    overrideMethodsByNative(XArray, Array, NATIVE_METHODS_ARRAY);

    // Date
    var XDate = XClass();
    XDate.extend(XObject.prototype);
    XDate.extend({
	isDate: function() {
	    return true;
	},
	toISOString: function() {
	    return this.getUTCFullYear() + '-' +
		X(this.getUTCMonth() + 1).padLeft("0", 2) + '-' +
		this.getUTCDateX().padLeft("0", 2) + 'T' +
		this.getUTCHoursX().padLeft("0", 2) + ':' +
		this.getUTCMinutesX().padLeft("0", 2) + ':' +
		this.getUTCSecondsX().padLeft("0", 2) + '.' +
		this.getUTFMillisecondsX().padLeft("0", 3) + 'Z';
	},
	isLeapYear: function() {
	    var year = this.__val.getFullYear();
	    return !(year%400) || (!(year%4) && !!(year%100)); // Boolean
	}
    }, true);
    overrideMethodsByNative(XDate, Date, NATIVE_METHODS_DATE);

    // RegExp
    var XRegExp = XClass();
    XRegExp.extend(XObject.prototype);
    XRegExp.extend({
	isRegExp: function() {
	    return true;
	}
    }, true);
    overrideMethodsByNative(XRegExp, RegExp, NATIVE_METHODS_REGEXP);

    var X = _global.X = function(value) {
	// already extended
	if (value && typeof value.isExtended === "function" && value.isExtended()) {
	    return value;
	}
	return extendObject(value);
    };
    X.extend = function(ClassName, isType) {
	var newClass = X[ClassName] = XClass();
	extendedClasses[ClassName] = {
	    isType: isType,
	    clazz: newClass
	};
	return newClass;
    };
    X.Undefined = XUndefined;
    X.Null = XNull;
    X.String = XString;
    X.Number = XNumber;
    X.Boolean = XBoolean;
    X.Function = XFunction;
    X.Array = XArray;
    X.Date = XDate;
    X.RegExp = XRegExp;
    X.Object = XObject;
    X.Class = XClass;
})(this);
